﻿namespace PugLib.Conditions
{
	using System;
	using System.Diagnostics.CodeAnalysis;
	using System.Linq.Expressions;
	using System.Reflection;

	public class Condition<T> : Condition
	{
		//Delegate that contains a compiled expression tree which can be run locally 
		internal Func<T, bool> Del;

		#region "Constructors"

		internal Condition(T owningObject, string propName, Filter condType, object value, Type valueType)
		{
			if (propName == null)
			{
				return;
			}

			//Split the string to handle nested property access 
			string[] s = propName.Replace(" ", string.Empty).Split('.');

			//Get the PropertyInfo instance for propName 
			PropertyInfo pInfo = owningObject.GetType().GetProperty(s[0]);
			ParameterExpression paramExpr = GetParamInstance(owningObject.GetType());
			MemberExpression callExpr = Expression.MakeMemberAccess(paramExpr, pInfo);

			//For each member specified, construct the additional MemberAccessExpression 
			//For example, if the user says "myCustomer.Order.OrderID = 4" we need an 
			//additional MemberAccessExpression for "Order.OrderID = 4" 
			for (int i = 1; i < s.Length; i++)
			{
				pInfo = pInfo.PropertyType.GetProperty(s[i]);
				callExpr = Expression.MakeMemberAccess(callExpr, pInfo);
			}

			//ConstantExpression representing the value on the left side of the operator 
			ConstantExpression valueExpr = Expression.Constant(value, valueType);

			Expression b = CombineExpression(callExpr, condType, valueExpr);
			LambdaExpr = Expression.Lambda<Func<T, bool>>(b, new[] { paramExpr });

			//Compile the lambda expression into a delegate 
			Del = LambdaExpr.Compile() as Func<T, bool>;
		}

		private Condition()
		{
		}

		#endregion

		#region "Methods"

		//Combines two conditions according to the specified operator 
		internal static Condition<T> Combine(Condition<T> cond1, Filter condType, Condition<T> cond2)
		{
			Condition<T> c = new Condition<T>();

			Expression b = CombineExpression(cond1.LambdaExpr.Body, condType, cond2.LambdaExpr.Body);

			ParameterExpression[] paramExpr = new[] { GetParamInstance(typeof(T)) };

			//Create the LambdaExpression and compile the delegate 
			c.LambdaExpr = Expression.Lambda<Func<T, bool>>(b, paramExpr);
			c.Del = CombineFunc(cond1.Del, condType, cond2.Del);

			return c;
		}

		//Combines multiple conditions according to the specified operator 
		internal static Condition<T> Combine(Condition<T> cond1, Filter condType, params Condition<T>[] conditions)
		{
			Condition<T> finalCond = cond1;
			foreach (Condition<T> c in conditions)
			{
				finalCond = Condition.Combine(finalCond, condType, c);
			}

			return finalCond;
		}

		//Run query locally instead of remotely 
		public bool Matches(T row)
		{
			return Del(row);
			//passes the row into the delegate to see if it's a match 
		}

		#endregion

		#region "Overloaded Operators"

		//Overloaded operators - allows syntax like "(condition1 Or condition2) And condition3" 

		[SuppressMessage("Microsoft.Usage", "CA2225:OperatorOverloadsHaveNamedAlternates")]
		public static Condition<T> operator &(Condition<T> c1, Condition<T> c2)
		{
			return Condition.Combine(c1, Filter.And, c2);
		}

		[SuppressMessage("Microsoft.Usage", "CA2225:OperatorOverloadsHaveNamedAlternates")]
		public static Condition<T> operator |(Condition<T> c1, Condition<T> c2)
		{
			return Condition.Combine(c1, Filter.Or, c2);
		}

		[SuppressMessage("Microsoft.Usage", "CA2225:OperatorOverloadsHaveNamedAlternates")]
		public static Condition<T> operator ^(Condition<T> c1, Condition<T> c2)
		{
			return Condition.Combine(c1, Filter.Xor, c2);
		}

		#endregion
	}

	public sealed class Condition<T, TValue> : Condition<T>
	{
		internal Condition(T owningObject, string propName, Filter condType, TValue value)
			: base(owningObject, propName, condType, value, typeof(TValue))
		{
		}

		public Func<T, bool> MakeFunc()
		{
			return Del;
		}

		internal LambdaExpression MakeExpression()
		{
			return LambdaExpr;
		}
	}
}